package com.toy.core.dao;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.DbCallback;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Order;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;

import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.mongodb.WriteResult;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;
import com.toy.core.dao.support.MongoSeq;
import com.toy.core.dao.support.Page;
import com.toy.core.domain.BaseObject;
import com.toy.core.util.ReflectionUtils;

public  class SimpleMongoRepository<T extends BaseObject, ID extends Serializable> implements MongoCrudRepository<T, ID> {
	
	@Autowired
	private MongoTemplate mongoTemplate;
	
	/*------------------------------------------*/
	public T save(T entity){
		BaseObject baseObject = (BaseObject)entity;
		if ( baseObject.getId() <= 0){
			baseObject.setId(MongoSeq.getSeq());
		}
		mongoTemplate.save(entity,getCollectionName());
		return entity;
	}
	
	public List<T> save(List<? extends T> entities){
		List<T> result = new ArrayList<T>();
		for (T entity : entities) {
			save(entity);
			result.add(entity);
		}
		return result;
	}
	
	/*------------------------------------------*/
	public T update(ID id,String propertyName,Object newValue){
		WriteResult writeResult = mongoTemplate.updateFirst(Query.query(Criteria.where(getIdName()).is(id)), Update.update(propertyName, newValue), getEntityClass());
		handleWriteResult(writeResult);
		return getById(id);
	}
	
	public T findAndModify(long idValue,Update update){
		return findAndModify(getIdName(),Long.valueOf(idValue),update);
	}
	
	public T findAndModify(String keyName,Object keyValue,Update update){
		List<Criteria> criteries = new ArrayList<Criteria>(1);
		criteries.add(Criteria.where(keyName).is(keyValue));
		return findAndModify(criteries,update);
	}
	
	public T findAndModify(List<Criteria> criteries,Update update){
		Query query = new Query();
		for (Criteria criteria : criteries) {
			query.addCriteria(criteria);
		}
		FindAndModifyOptions options = FindAndModifyOptions.options().remove(false).upsert(true).returnNew(true);
		return mongoTemplate.findAndModify(query,update,options, getEntityClass());
	}
	
	
	/*------------------------------------------*/
	public boolean isUnique(Object entity,String uniquePropertyNames){
		//TODO
		return true;
	}
	
	public boolean isUnique(Map<String,Object> uniqueProperties,ID id){
		//TODO
		return true;
	}
	/*------------------------------------------*/
	public T getById(ID id){
		Assert.notNull(id, "The given id must not be null!");
		return mongoTemplate.findById(id, getEntityClass());
	}
	
	public T getUniqueByProperty(String propertyName,Object value){
		return mongoTemplate.findOne(Query.query(Criteria.where(propertyName).is(value)), getEntityClass());
	}
	
	public T getUniqueByCriteria(final Criteria... criterions){
		Query query = new Query();
		for (Criteria criteria : criterions) {
			query.addCriteria(criteria);
		}
		return mongoTemplate.findOne(query, getEntityClass());
	}
	
	/*------------------------------------------*/
	public List<T> findAll(){
		return mongoTemplate.findAll(getEntityClass());
	}
	
	public List<T> findByIds(List<ID> ids){
		return mongoTemplate.find(Query.query(Criteria.where(getIdName()).in(ids)), getEntityClass());
	}
	
	public List<T> findByProperty(String propertyName,Object value){
		return mongoTemplate.find(Query.query(Criteria.where(propertyName).is(value)), getEntityClass());
	}
	
	public List<T> findByProperty(String propertyName, Object value,String orderBy, boolean isAsc){
		Query query = Query.query(Criteria.where(propertyName).is(value));
		query.sort().on(orderBy, isAsc?Order.ASCENDING:Order.DESCENDING);
		return mongoTemplate.find(query, getEntityClass());
	}
	
	public List<T> findByCriteria(List<Criteria> criterions){
		Query query = new Query();
		for (Criteria criteria : criterions) {
			query.addCriteria(criteria);
		}
		return mongoTemplate.find(query, getEntityClass());
	}
	
	public List<T> findByCriterion(List<Criteria> criterions, String orderBy,boolean isAsc){
		Query query = new Query();
		for (Criteria criteria : criterions) {
			query.addCriteria(criteria);
		}
		query.sort().on(orderBy, isAsc?Order.ASCENDING:Order.DESCENDING);
		return mongoTemplate.find(query, getEntityClass());
	}
	
	/*------------------------------------------*/
	public Page<T> findPaged(int pageNo, int pageSize){
		return findPaged(null, getIdName(), false, pageNo, pageSize);
	}
	
	public Page<T> findPaged(Query query,int pageNo, int pageSize){
		long totalCount = mongoTemplate.count(query, getEntityClass());
		if (totalCount < 1)
			return new Page<T>();
		int startIndex = Page.getStartOfPage(pageNo, pageSize);
		query.skip(startIndex).limit(pageSize);
		List<T> data = mongoTemplate.find(query, getEntityClass());
		return new Page<T>(startIndex,totalCount,pageSize,data);
	}
	
	public Page<T> findPaged(List<Criteria> criterions, String orderBy,boolean isAsc,int pageNo, int pageSize){
		Query query = new Query();
		if ( criterions != null && criterions.size() > 0 ){
			for (Criteria criteria : criterions) {
				query.addCriteria(criteria);
			}
		}
		long totalCount = mongoTemplate.count(query, getEntityClass());
		if (totalCount < 1)
			return new Page<T>();
		int startIndex = Page.getStartOfPage(pageNo, pageSize);
		query.skip(startIndex).limit(pageSize);
		if ( isAsc ){
			query.sort().on(orderBy, Order.ASCENDING);
		}else{
			query.sort().on(orderBy, Order.DESCENDING);
		}
		List<T> data = mongoTemplate.find(query, getEntityClass());
		return new Page<T>(startIndex,totalCount,pageSize,data);
	}
	
	/*------------------------------------------*/
	public void delete(ID id){
		Assert.notNull(id, "The given id must not be null!");
		delete(getById(id));
	}
	
	public void delete(T entity){
		mongoTemplate.remove(entity);
	}
	
	public void delete(List<? extends T> entities){
		for (T entity : entities) {
			delete(entity);
		}
	}
	
	public void deleteAll(){
		mongoTemplate.dropCollection(getEntityClass());
	}
	
	public void deleteByProperty(String propertyName, Object value){
		mongoTemplate.remove(Query.query(Criteria.where("propertyName").is(value)), getEntityClass());
	}
	
	/*------------------------------------------*/
	public String saveGridFS(final InputStream is,final String fileName){
		return mongoTemplate.execute(new DbCallback<String>(){
			public String doInDB(DB db) throws MongoException, DataAccessException {
				GridFS gridFS = new GridFS(db);
				GridFSInputFile gridFSInputFile =  gridFS.createFile(is,fileName);
				gridFSInputFile.save();
				ObjectId objectId = (ObjectId) gridFSInputFile.getId();
				return objectId.toString();
			}});
	}
	
	public GridFSDBFile getGridFSDBFileByObjectId(final String objectId){
		return mongoTemplate.execute(new DbCallback<GridFSDBFile>(){
			public GridFSDBFile doInDB(DB db) throws MongoException,
					DataAccessException {
				GridFS gridFS = new GridFS(db);
				return gridFS.find(new ObjectId(objectId));
			}});
	}
	
	public Map<String,GridFSDBFile> findGridFSDBFile(final List<String> idList){
		final DBObject query = BasicDBObjectBuilder.start().push("_id").append("$in", idList).pop().get();
		return mongoTemplate.execute(new DbCallback<Map<String,GridFSDBFile>>(){
			public Map<String,GridFSDBFile> doInDB(DB db) throws MongoException, DataAccessException {
				GridFS gridFS = new GridFS(db);
				List<GridFSDBFile> fsFiles = gridFS.find(query);
				Map<String,GridFSDBFile> data = new HashMap<String, GridFSDBFile>(idList.size());
				for (int i = 0; i < fsFiles.size(); i++) {
					data.put(idList.get(i), fsFiles.get(i));
				}
				return data;
			}
		});
	}
	
	public void deleteGridFS(final String objectId){
		mongoTemplate.execute(new DbCallback<T>(){
			public T doInDB(DB db) throws MongoException, DataAccessException {
				GridFS gridFS = new GridFS(db);
				gridFS.remove(new ObjectId(objectId));
				return null;
			}});
	}
	/*------------------------------------------*/
	
	/**
	 * 统一处理writeResult;
	 */
	protected void handleWriteResult(WriteResult writeResult){
		writeResult.getCachedLastError().throwOnError();
	}
	
	/**
	 * 获得ID的属性名
	 */
	public String getIdName(){
		//以下两种方法都可以
		return mongoTemplate.getConverter().getMappingContext().getPersistentEntity(getEntityClass()).getIdProperty().getFieldName();
		//return "_id";
	}
	
	/**
	 *获得泛型的实体对象
	 */
	public Class<T> getEntityClass(){
		return ReflectionUtils.getSuperClassGenricType(getClass());
	}
	
	private String getCollectionName(){
		return mongoTemplate.getCollectionName(getEntityClass());
	}

}
